
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
 */
package be.ac.ua.pats.thesisrobotica.device;

//~--- non-JDK imports --------------------------------------------------------


import be.ac.ua.pats.rr.entity.AbstractSensor;
import be.ac.ua.pats.rr.entity.IEntity;
import be.ac.ua.pats.rr.entity.InitialisationException;
import be.ac.ua.pats.rr.entity.NotInitialisedException;
import be.ac.ua.pats.rr.entity.RRVarUnknownException;
import be.ac.ua.pats.rr.entity.UnableToModifyRRVarException;
import be.ac.ua.pats.thesisrobotica.device.communication.CommandFactory;
import be.ac.ua.pats.thesisrobotica.device.communication.Connection;
import be.ac.ua.pats.thesisrobotica.device.constants.RobotConstants;


//~--- JDK imports ------------------------------------------------------------
import be.ac.ua.pats.thesisrobotica.device.exceptions.DuplicateException;
import be.ac.ua.pats.thesisrobotica.device.exceptions.InitializationException;

import java.io.File;
import java.io.IOException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.concurrent.ExecutorService;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.namespace.QName;

/**
 *
 * @author Admin
 */
public class NXTRobot implements IEntity {
    String                            robotId     = "";
    boolean                           initialized = false;
    File                              benchmark;
    CommandFactory                    commandFactory;
    Connection                        connection;
    ExecutorService                   execService;
    ArrayList<QName>                  idList;
    Hashtable<String, AbstractSensor> sensorList;

    public NXTRobot() {
        sensorList = new Hashtable<String, AbstractSensor>();
        idList     = new ArrayList<QName>();
    }

    /**
     * Initializes the NXTRobot by connecting to the remote device and
     * creating a commandfactory (used to send commands).
     *
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public void initialise() throws InitialisationException {
        try {
            this.connect();
            System.out.println("Creating commandFactory");

            commandFactory = new CommandFactory(connection);

            System.out.println("Robot: connect(): Requesting robot-ID");
            getRobotId();
            System.out.println("Robot: connect(): Robot-ID acquired");

            initialized = true;
        } catch (IOException ex) {
            System.out.println("IOException: " + ex.getLocalizedMessage());
        } catch (InterruptedException ex) {
            System.out.println("InterruptedException: " + ex.getLocalizedMessage());
        }
    }

    public synchronized void setRobotId(String robotId) {
        this.robotId = robotId;

        notifyAll();
        System.out.println("Robot: Robot ID set");
    }

    public void setBenchMark(File benchmark) {
        this.benchmark = benchmark;
    }

    public void addSensor(AbstractSensor sensor, String id, String type) {
        sensorList.put(id, sensor);
    }

    /**
     * Requests a GET for the robot-ID.
     *
     * @throws IOException
     * @throws InterruptedException
     */
    public synchronized void getRobotId() {
        try {
            System.out.println("Requesting robot ID");

            Object[] message = { RobotConstants.Communication.FirstToken.GET,
                                 RobotConstants.Communication.SecondToken.ID,
                                 RobotConstants.Communication.ThirdToken.ROBOT };

            commandFactory.setAndSend(message);
            System.out.println("Waiting for reply");

            try {
                this.wait();
            } catch (InterruptedException ex) {
                System.out.println("InterruptedException in getRobotId()");
            }

            System.out.println("Reply received");
        } catch (IOException ex) {
            System.out.println("IOException in NXTRobot: getRobotId()");
        }
    }

    /**
     * Requests a GET of the sensor-benchmark
     *
     * @param id The sensor-ID.
     * @return A File containing the benchmark graph in pdf-format.
     * @throws IOException
     * @throws InterruptedException
     */
    public synchronized File getBenchmark(String id) {
        try {
            System.out.println("Requesting benchmark for sensorId " + id);
            connection.send(RobotConstants.Communication.FirstToken.GET + ","
                            + RobotConstants.Communication.SecondToken.BENCHMARK + "," + id);
            System.out.println("Waiting for reply");
            this.wait();
            System.out.println("Reply received");

            return benchmark;
        } catch (InterruptedException ex) {
            System.out.println("InterruptedException in NXTRobot:getBenchmark()");

            return null;
        } catch (IOException ex) {
            System.out.println("IOException in NXTRobot:getBenchmark()");

            return null;
        }
    }

    /**
     * Returns a hashtable of all the received sensors.
     * NXTRobot needs to be initialized before using this method.
     *
     * @return The hastable of sensors.
     * @throws InitializationException When the sensorlist hasn't been initialized yet.
     */
    public synchronized Hashtable<String, AbstractSensor> getSensors() {
        if (initialized) {
            return sensorList;
        } else {
            try {
                throw new InitializationException();
            } catch (InitializationException ex) {
                System.out.println("InitializationException in NXTRobot:getSensors()");

                return null;
            }
        }
    }

    /**
     * Requests a GET of a sensor-value.
     *
     * @param id The sensor-ID.
     * @return The received value.
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public synchronized String getValue(QName qName) throws RRVarUnknownException, NotInitialisedException {
        try {
            System.out.println("Requesting value for sensorId " + qName.getLocalPart());
            connection.send(RobotConstants.Communication.FirstToken.GET + ","
                            + RobotConstants.Communication.SecondToken.VALUE + "," + qName.getLocalPart());

            return null;
        } catch (IOException ex) {
            Logger.getLogger(NXTRobot.class.getName()).log(Level.SEVERE, null, ex);

            return null;
        }
    }

    /**
     * Connects the robot.
     * Requests the robot-ID and starts the connection-thread.
     *
     * @throws IOException
     */
    public void connect() throws IOException, InterruptedException {
        System.out.println("Robot: connect()");
        System.out.println("Robot: connect(): Creating new connection");

        connection = new Connection(this);

        System.out.println("Robot: connect(): Starting connection thread");
        (new Thread(connection)).start();
    }

    /**
     * Disconnects the robot.
     *
     * @throws IOException
     */
    public void disconnect() throws IOException {
        System.out.println("Disconnecting");
        connection.disconnect();

        initialized = false;
    }

    /**
     * @return The current connection.
     */
    public Connection getConnection() {
        return connection;
    }

    /**
     * Request a SET of a sensor-value.
     *
     * @param id The sensor-ID.
     * @param value The value to be set.
     * @throws IOException
     */
    @Override
    public void setValue(QName qName, String value)
            throws RRVarUnknownException, UnableToModifyRRVarException, NotInitialisedException {
        try {
            if (initialized) {
                System.out.println("Setting value of sensorId " + qName.getLocalPart() + " to " + value);
                connection.send(RobotConstants.Communication.FirstToken.SET + ","
                                + RobotConstants.Communication.SecondToken.VALUE + "," + qName.getLocalPart() + ","
                                + value);
            } else {
                throw new NotInitialisedException();
            }
        } catch (IOException ex) {
            Logger.getLogger(NXTRobot.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Requests a SET of an sensor-interval.
     *
     * @param id The sensor-ID.
     * @param interval The interval to be set.
     * @throws IOException
     */
    public void setInterval(String id, int interval) throws IOException, InitializationException {
        if (initialized) {
            connection.send(RobotConstants.Communication.FirstToken.SET + ","
                            + RobotConstants.Communication.SecondToken.INTERVAL + "," + id + ","
                            + String.valueOf(interval));
        } else {
            throw new InitializationException();
        }
    }

    /**
     * Requests a SET of a sensor-threshold.
     *
     * @param id The sensor-ID.
     * @param threshold The threshold to be set.
     * @throws IOException
     */
    public void setThreshold(String id, int threshold) throws IOException, NotInitialisedException {
        if (initialized) {
            connection.send(RobotConstants.Communication.FirstToken.SET + ","
                            + RobotConstants.Communication.SecondToken.THRESHOLD + "," + id + ","
                            + String.valueOf(threshold));
        } else {
            throw new NotInitialisedException();
        }
    }

    @Override
    public Collection<QName> getList() throws NotInitialisedException {
        try {
            if (initialized) {
                Object[] message = { RobotConstants.Communication.FirstToken.GET,
                                     RobotConstants.Communication.SecondToken.ID,
                                     RobotConstants.Communication.ThirdToken.ALL_SENSORS };

                commandFactory.setAndSend(message);
                System.out.println("Waiting for QName-list from robot");
                try {
                    this.wait();
                } catch(InterruptedException ex) {
                    System.out.println("InterruptedException in NXTRobot: getList()");
                }
                System.out.println("Reply received");

                return idList;
            } else {
                throw new NotInitialisedException();
            }
        } catch (IOException ex) {
            System.out.println("IOException: " + ex.getLocalizedMessage());

            return null;
        }
    }

    @Override
    public QName getId() throws NotInitialisedException {
        return new QName(RobotConstants.namespaceURI + robotId);
    }

    public synchronized void finalizeSensorList() {
        notifyAll();
    }

    @Override
    public boolean isInitialised() {
        return initialized;
    }

    public void addSensorName(String id) throws InitializationException, DuplicateException {
        if (initialized) {
            QName qName = new QName(RobotConstants.namespaceURI + robotId, id);

            if (!idList.contains(qName)) {
                idList.add(new QName(RobotConstants.namespaceURI + robotId, id));
            } else {
                throw new DuplicateException();
            }
        } else {
            throw new InitializationException();
        }
    }

    @Override
    public void destroy() throws NotInitialisedException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public AbstractSensor getSensor(QName q) throws be.ac.ua.pats.rr.entity.NotInitialisedException, be.ac.ua.pats.rr.entity.RRVarUnknownException {
        throw new UnsupportedOperationException("Not supported yet.");
    }
}
